#include "StdAfx.h"
#include "MTDriveServer.h"
#include <iostream>
#include <math.h>
#include <WinAble.h> // Required for the ::SendInput function



// Class CMTCritSec/ CMMAutoLock
//
#if defined(DEBUG) || defined(_DEBUG)

CMTCritSec::CMTCritSec()
{
	InitializeCriticalSection(&m_CritSec);
	m_currentOwner = m_lockCount = 0;
	m_fTrace = FALSE;
}

CMTCritSec::~CMTCritSec()
{
	DeleteCriticalSection(&m_CritSec);
}

void CMTCritSec::Lock()
{
	DWORD us = GetCurrentThreadId();
	DWORD currentOwner = m_currentOwner;
	if (currentOwner && (currentOwner != us))
	{			
		if(m_fTrace) // already owned, but not by us
		{
#ifdef ENABLE_LOCK_TRACING
			TCHAR str[256];
			swprintf(str, 256, L"Thread %d about to wait for lock %x owned by %d", GetCurrentThreadId(), &m_CritSec, m_currentOwner);
			OutputDebugString(str);
#endif ENABLE_LOCK_TRACING			
		}
	}
	EnterCriticalSection(&m_CritSec);
	if (0 == m_lockCount++) 
	{
		// we now own it for the first time.  Set owner information
		m_currentOwner = us;
		if (m_fTrace)
		{
#ifdef ENABLE_LOCK_TRACING
			TCHAR str[256];
			swprintf(str, 256, L"Thread %d now owns lock %x", m_currentOwner, &m_CritSec);
			OutputDebugString(str);
#endif ENABLE_LOCK_TRACING
		}
	}
}

void CMTCritSec::Unlock()
{
	if (0 == --m_lockCount)
	{	
		if (m_fTrace)
		{	
#ifdef ENABLE_LOCK_TRACING
			TCHAR str[256];
			swprintf(str, 256, L"Thread %d releasing lock %x", m_currentOwner, &m_CritSec);
			OutputDebugString(str);
#endif ENABLE_LOCK_TRACING
		}

		m_currentOwner = 0;
	}
	LeaveCriticalSection(&m_CritSec);
}

bool CMTCritSec::CritCheckIn(CMTCritSec * pcCrit)
{
	return (GetCurrentThreadId() == pcCrit->m_currentOwner);
}

bool CMTCritSec::CritCheckOut(CMTCritSec * pcCrit)
{
	return (GetCurrentThreadId() != pcCrit->m_currentOwner);
}	

void CMTCritSec::DbgLockTrace(CMTCritSec *pcCrit, bool fTrace)
{
	pcCrit->m_fTrace = fTrace;
}

#else DEBUG


CMTCritSec::CMTCritSec()
{
	InitializeCriticalSection(&m_CritSec);
}

CMTCritSec::~CMTCritSec()
{
	DeleteCriticalSection(&m_CritSec);
}

void CMTCritSec::Lock()
{
	EnterCriticalSection(&m_CritSec);
}

void CMTCritSec::Unlock()
{
	LeaveCriticalSection(&m_CritSec);
}

#endif DEBUG

CMTDriveServer::CMTDriveServer(void):	
	m_pOSCPacket(NULL),
	m_TcpServer(NULL),
	m_UdpServer(NULL),
	m_cmtSensor(NULL),
	m_bRunThread(true),
	m_msgDrain(NULL),
	m_iDlgItemID(0),
	m_thread(NULL)
{
	ZeroMemory(&m_props, sizeof(CMTCMTDriveServerProps));
	ZeroMemory(&m_cmtCursor, sizeof(CMTCursor));
	ZeroMemory(&m_cmtCursorChar, sizeof(CMTCursorChar));
	ZeroMemory(&m_cmtSession, sizeof(CMTSession));
}

CMTDriveServer::~CMTDriveServer(void)
{
	ShutDown();
}

void CMTDriveServer::PrintServerState(LPCTSTR strMsg)
{
	if(m_msgDrain && m_iDlgItemID)
	{
		CMTAutoLock lck(&m_csPrint);
		::SetDlgItemText(m_msgDrain, m_iDlgItemID, strMsg);
	}
}

bool CMTDriveServer::InitServer(const CMTCMTDriveServerProps &props, HWND hwnd, int iID)
{
	m_msgDrain = hwnd;
	m_iDlgItemID = iID;

	PrintServerState(_T("Initialize SMT server."));

	if(m_TcpServer)
		ShutDown();
	if(m_UdpServer)
		ShutDown();	

	ZeroMemory(&m_cmtCursor, sizeof(CMTCursor));
	ZeroMemory(&m_cmtCursorChar, sizeof(CMTCursorChar));
	ZeroMemory(&m_cmtSession, sizeof(CMTSession));

	m_props = props;

	char *hostName = "127.0.0.1";
	sprintf_s(m_cmtSession.cstrHost, strlen(hostName)+1, "%s", hostName);
	m_cmtSession.lPort = m_props.lPort;
	m_cmtSession.lFseq = 0;
	sprintf_s(m_cmtSession.cPort, MAX_FLOAT_SIZE, "%d", m_props.lPort);
	sprintf_s(m_cmtSession.cFseq, MAX_FLOAT_SIZE, "%d", 0);
	
	m_strXML.clear();
	m_pointMouse.x = m_pointMouse.y = 0;

	if(m_props.oscFormat == OSC_RAW)
		m_pOSCPacket = new osc::OutboundPacketStream(m_buffer, MAX_BUF_SIZE);

	if(m_props.bUDP)
	{
		IpEndpointName host( hostName, m_props.lPort);
		m_UdpServer = new UdpTransmitSocket(host);
	}
	else
	{
		m_TcpServer = new TcpServer();
		m_TcpServer->Start(m_props.lPort);
	}

	// Create a sensor without setting a callback handler, since we are going to pull the current device state by ourself.
	//
	m_cmtSensor = SMT_Open(0, m_props.lWidth, m_props.lHeight, 0, 0);
	if(!m_cmtSensor)
	{
		OutputDebugString(_T("couldn't connect to default SMK device\n"));		
		ShutDown();
		PrintServerState(_T("Could not find any SMT device! May there is no device connected."));
		return false;
	}

	StartServer();
	return true;
}

bool CMTDriveServer::UpdateServer(const CMTCMTDriveServerProps &props)
{
	bool bRestart = m_bRunThread;
	StopServer();

	// Later this will be set by the calling app!
	//
	char *hostName = "127.0.0.1";

	// Do we need to change anything concerning the network setup?
	//
	if(m_props.lPort != props.lPort || m_props.dwIP != props.dwIP || m_props.bUDP != props.bUDP)
	{		
		if(m_TcpServer)
		{
			m_TcpServer->Stop();
			delete m_TcpServer;
			m_TcpServer = NULL;
		}
		if(m_UdpServer)
		{
			delete m_UdpServer;
			m_UdpServer = NULL;
		}
		
		if(props.bUDP)
		{
			IpEndpointName host( hostName, props.lPort);
			m_UdpServer = new UdpTransmitSocket(host);
		}
		else
		{
			m_TcpServer = new TcpServer();
			m_TcpServer->Start(props.lPort);
		}
	}	

	// Change the OSC output format
	//
	if(m_props.oscFormat != props.oscFormat)
	{
		if(m_pOSCPacket)
		{
			delete m_pOSCPacket;
			m_pOSCPacket = NULL;
		}

		m_strXML.clear();

		if(props.oscFormat == OSC_RAW)
			m_pOSCPacket = new osc::OutboundPacketStream(m_buffer, MAX_BUF_SIZE);
	}

	// Set new dimension on the device
	//
	if(m_props.lWidth != props.lWidth || m_props.lHeight != props.lHeight)
		SMT_SetSensorWindowDimension(m_cmtSensor, props.lHeight, props.lHeight);

	// Reset the internal members
	//
	ZeroMemory(&m_cmtCursor, sizeof(CMTCursor));
	ZeroMemory(&m_cmtCursorChar, sizeof(CMTCursorChar));
	ZeroMemory(&m_cmtSession, sizeof(CMTSession));

	m_props = props;
	sprintf_s(m_cmtSession.cstrHost, strlen(hostName)+1, "%s", hostName);
	m_cmtSession.lPort = m_props.lPort;
	m_cmtSession.lFseq = 0;
	sprintf_s(m_cmtSession.cPort, MAX_FLOAT_SIZE, "%d", m_props.lPort);
	sprintf_s(m_cmtSession.cFseq, MAX_FLOAT_SIZE, "%d", 0);

	m_pointMouse.x = m_pointMouse.y = 0;

	if(bRestart)
		StartServer();	

	return true;
}

bool CMTDriveServer::StartServer()
{
	PrintServerState(_T("Start SMT server."));

	// Worker thread calling the SMTUpdate() function to force calls of our callback.
	//
	m_bRunThread = true;
	m_thread = ::CreateThread(NULL, NULL, CMTDriveServer::UpdataSensor, this, NULL, NULL);
	if(!m_thread)
	{
		ShutDown();
		_ASSERTE(0);
		return false;
	}
	return true;
}

bool CMTDriveServer::StopServer()
{
	PrintServerState(_T("Stop SMT server."));

	// Stop the worker thread as first.
	//
	m_bRunThread = false;	

	// Wait until the thread is done!
	//
	CMTAutoLock lck(&m_cs);	

	if(m_thread)
	{
		::CloseHandle(m_thread);
		m_thread = NULL;
	}

	return true;
}

bool CMTDriveServer::ShutDown()
{
	PrintServerState(_T("ShutDown SMT server."));

	StopServer();

	if(m_cmtSensor)
	{
		SMT_Close(m_cmtSensor);
		m_cmtSensor = NULL;
	}

	if(m_pOSCPacket)
	{
		delete m_pOSCPacket;
		m_pOSCPacket = NULL;
	}

	if(m_TcpServer)
	{
		m_TcpServer->Stop();
		delete m_TcpServer;
		m_TcpServer = NULL;
	}
	if(m_UdpServer)
	{
		delete m_UdpServer;
		m_UdpServer = NULL;
	}

	return true;
}

DWORD WINAPI CMTDriveServer::UpdataSensor(void *pData)
{
	if(!pData)
	{
		_ASSERTE(0);
		return 0;
	}

	CMTDriveServer *pThis = static_cast<CMTDriveServer*>(pData);
	bool run = true;

	pThis->PrintServerState(_T("Run SMT server process."));

	CMTAutoLock lck(&pThis->m_cs);	

	while(pThis->m_bRunThread && run)
	{
		pThis->m_vCursors.clear();

		if(!SMT_Update(pThis->m_cmtSensor))
			run = false;

		// iterate on cursor list, and draw each cursor that's currently in SMT_CURSOR_DOWN state
		//
		SMT_CURSOR c = SMT_GetSensorCursors(pThis->m_cmtSensor);
		while(c)
		{
			// skip SMT_CURSOR_UP cursors waiting for multiclick
			//
			SMT_EVENT curEvent = SMT_GetCursorState(c);
			if (curEvent == SMT_CURSOR_DOWN)
			{
				pThis->m_vCursors.push_back(c);
			}
			else if(curEvent == SMT_CURSOR_UP)
			{
				pThis->m_pointMouse.x = 0;
				pThis->m_pointMouse.y = 0;
			}

			c = SMT_GetCursorNext(c);
		}

		if(pThis->m_props.oscFormat == OSC_RAW)
			run = pThis->UpdateCursorOSC();
		else if(pThis->m_props.oscFormat == OSC_XML)
			run = pThis->UpdateCursorXML();
	}

	return 0;
}

bool CMTDriveServer::UpdateCursorXML()
{
	if(m_props.oscFormat == OSC_RAW)
		return false;	

	// OSCPacket begin
	//
	m_strXML.clear();
	m_strXML = "<OSCPACKET ADDRESS=\"";
	m_strXML += m_cmtSession.cstrHost;
	m_strXML += "\" PORT=\"";
	m_strXML += m_cmtSession.cPort;
	m_strXML += "\" TIME=\"1\">";

	// Message "set"
	//		
	for(size_t s=0; s < m_vCursors.size(); ++s)
	{
		m_cmtCursor.ID = SMT_GetCursorID(m_vCursors[s]);

		int currentX = 0;
		int currentY = 0;

		if(m_props.bFlipH)
			currentX = m_props.lWidth - SMT_GetCursorX(m_vCursors[s]);
		else
			currentX = SMT_GetCursorX(m_vCursors[s]);
		if(m_props.bFlipV)
			currentY = m_props.lHeight - SMT_GetCursorY(m_vCursors[s]);
		else
			currentY = SMT_GetCursorY(m_vCursors[s]);

		if(m_props.bRelative)
		{
			m_cmtCursor.x = ((float)currentX) / (float)m_props.lWidth;
			m_cmtCursor.y = ((float)currentY) / (float)m_props.lHeight;
		}
		else
		{
			m_cmtCursor.x = (float)currentX;
			m_cmtCursor.y = (float)currentY;
		}

		if(s == 0 && m_props.bSysMouse)
			FireSysMouseEvent(SMT_GetCursorX(m_vCursors[s]), SMT_GetCursorY(m_vCursors[s]));
		
		m_cmtCursor.dx = 0;
		m_cmtCursor.dy = 0;		
		m_cmtCursor.m = 0; //sqrtf((m_cmtCursor.dx * m_cmtCursor.dx) + (m_cmtCursor.dy * m_cmtCursor.dy));	// Motion acceleration

		sprintf_s(m_cmtCursorChar.ccID, MAX_FLOAT_SIZE, "%d", m_cmtCursor.ID);
		sprintf_s(m_cmtCursorChar.cX, MAX_FLOAT_SIZE, "%f", m_cmtCursor.x);
		sprintf_s(m_cmtCursorChar.cY, MAX_FLOAT_SIZE, "%f", m_cmtCursor.y);
		sprintf_s(m_cmtCursorChar.cDX, MAX_FLOAT_SIZE, "%f", m_cmtCursor.dx);
		sprintf_s(m_cmtCursorChar.cDY, MAX_FLOAT_SIZE, "%f", m_cmtCursor.dy);
		sprintf_s(m_cmtCursorChar.cM, MAX_FLOAT_SIZE, "%f", m_cmtCursor.m);

		m_strXML += "\n<MESSAGE NAME=\"/tuio/2Dcur\">";
		m_strXML += "\n<ARGUMENT TYPE=\"s\" VALUE=\"set\" />";
		m_strXML += "\n<ARGUMENT TYPE=\"i\" VALUE=\"";
		m_strXML += m_cmtCursorChar.ccID;
		m_strXML += "\" />";
		m_strXML += "\n<ARGUMENT TYPE=\"f\" VALUE=\"";
		m_strXML += m_cmtCursorChar.cX;
		m_strXML += "\" />";
		m_strXML += "\n<ARGUMENT TYPE=\"f\" VALUE=\"";
		m_strXML += m_cmtCursorChar.cY;
		m_strXML += "\" />";
		m_strXML += "\n<ARGUMENT TYPE=\"f\" VALUE=\"";
		m_strXML += m_cmtCursorChar.cDX;
		m_strXML += "\" />";
		m_strXML += "\n<ARGUMENT TYPE=\"f\" VALUE=\"";
		m_strXML += m_cmtCursorChar.cDY;
		m_strXML += "\" />";
		m_strXML += "\n<ARGUMENT TYPE=\"f\" VALUE=\"";
		m_strXML += m_cmtCursorChar.cM;
		m_strXML += "\" />";
		m_strXML += "\n</MESSAGE>";
	}

	// "alive" Message
	//
	m_strXML += "\n<MESSAGE NAME=\"/tuio/2Dcur\">\n<ARGUMENT TYPE=\"s\" VALUE=\"alive\" />";
	{
		for(size_t s=0; s < m_vCursors.size(); ++s)
		{
			m_cmtCursor.ID = SMT_GetCursorID(m_vCursors[s]);
			sprintf_s(m_cmtCursorChar.ccID, MAX_FLOAT_SIZE, "%d", m_cmtCursor.ID);
			m_strXML +="\n<ARGUMENT TYPE=\"i\" VALUE=\"";
			m_strXML += m_cmtCursorChar.ccID;
			m_strXML += "\" />";
		}	
	}	
	m_strXML += "\n</MESSAGE>";

	// "fseq" Message
	//
	m_strXML += "\n<MESSAGE NAME=\"/tuio/2Dcur\">";
	m_strXML += "<ARGUMENT TYPE=\"s\" VALUE=\"fseq\" />";
	m_strXML += "\n<ARGUMENT TYPE=\"i\" VALUE=\"";
	m_strXML += m_cmtSession.cFseq;
	m_strXML += "\" />";
	m_strXML += "\n</MESSAGE>";

	// OSCPacket end
	//
	m_strXML += "\n</OSCPACKET>\n";
	m_strXML += '\0';

	m_cmtSession.lFseq ++;
	sprintf_s(m_cmtSession.cFseq, MAX_FLOAT_SIZE, "%d", m_cmtSession.lFseq);

	if(m_props.bUDP)
	{
		if(m_UdpServer)
			m_UdpServer->Send(m_strXML.c_str(), m_strXML.size());
	}
	else
	{
		if(m_TcpServer)
			m_TcpServer->Send(m_strXML.c_str(), m_strXML.size());
	}

	return true;
}

bool CMTDriveServer::UpdateCursorOSC()
{
	if(m_props.oscFormat == OSC_XML)
		return false;

	m_pOSCPacket->Clear();
	*m_pOSCPacket << osc::BeginBundle();
	
	// Message "set"
	//		
	for(size_t s=0; s < m_vCursors.size(); ++s)
	{
		m_cmtCursor.ID = SMT_GetCursorID(m_vCursors[s]);

		int currentX = 0;
		int currentY = 0;

		if(m_props.bFlipH)
			currentX = m_props.lWidth - SMT_GetCursorX(m_vCursors[s]);
		else
			currentX = SMT_GetCursorX(m_vCursors[s]);
		if(m_props.bFlipV)
			currentY = m_props.lHeight - SMT_GetCursorY(m_vCursors[s]);
		else
			currentY = SMT_GetCursorY(m_vCursors[s]);

		if(m_props.bRelative)
		{
			m_cmtCursor.x = ((float)currentX) / (float)m_props.lWidth;
			m_cmtCursor.y = ((float)currentY) / (float)m_props.lHeight;
		}
		else
		{
			m_cmtCursor.x = (float)currentX;
			m_cmtCursor.y = (float)currentY;
		}

		if(s == 0 && m_props.bSysMouse)
			FireSysMouseEvent(SMT_GetCursorX(m_vCursors[s]), SMT_GetCursorY(m_vCursors[s]));

		m_cmtCursor.dx = 0;
		m_cmtCursor.dy = 0;		
		m_cmtCursor.m = 0; //sqrtf((m_cmtCursor.dx * m_cmtCursor.dx) + (m_cmtCursor.dy * m_cmtCursor.dy));	// Motion acceleration

		*m_pOSCPacket << osc::BeginMessage( "/tuio/2Dcur" ) << "set" << m_cmtCursor.ID << m_cmtCursor.x << m_cmtCursor.y << m_cmtCursor.dx << m_cmtCursor.dy << m_cmtCursor.m << osc::EndMessage;	
	}	

	// "alive" Message
	//
	for(size_t s=0; s < m_vCursors.size(); ++s)
	{
		*m_pOSCPacket << osc::BeginMessage( "/tuio/2Dcur" ) << "alive" << SMT_GetCursorID(m_vCursors[s]) << osc::EndMessage;
	}
	

	// "fseq" Message
	//
	*m_pOSCPacket << osc::BeginMessage( "/tuio/2Dcur" ) << "fseq" << (long)m_cmtSession.lFseq << osc::EndMessage;

	*m_pOSCPacket << osc::EndBundle;

	m_cmtSession.lFseq ++; 
	if(m_pOSCPacket->IsReady())
	{
		if(m_props.bUDP)
		{
			if(m_UdpServer)
				m_UdpServer->Send(m_pOSCPacket->Data(), m_pOSCPacket->Size());
		}
		else
		{
			if(m_TcpServer)
				m_TcpServer->Send(m_pOSCPacket->Data(), m_pOSCPacket->Size());
		}
	}

	return true;
}

void CMTDriveServer::FireSysMouseEvent(int iX,int iY)
{
	// ToDo: How do I fire a system mouse event???

	INPUT mouseInput;
	mouseInput.type = INPUT_MOUSE;
	mouseInput.mi.dx = m_pointMouse.x == 0 ? 0 : m_pointMouse.x - iX;
	mouseInput.mi.dy = m_pointMouse.y == 0 ? 0 : m_pointMouse.y - iY;
	mouseInput.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK;
	mouseInput.mi.mouseData = 0;
	mouseInput.mi.time = 0;
	mouseInput.mi.dwExtraInfo = 0;

	::SendInput(1, &mouseInput, sizeof(mouseInput));

	m_pointMouse.x = iX;
	m_pointMouse.y = iY;
}
